package com.bhh.bio;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author bhh
 * @description 关于BIO模型的服务器端实现
 * 和 ChatHandler类
 * 和客户端 ChatClient类 + UserInputHandler类配合使用
 *
 * @date Created in 2021-04-07 15:08
 * @modified By
 */
@Slf4j
public class ChatServer {

    private final int PORT = 8888;
    private final String QUIT = "quit";

    private ServerSocket serverSocket;
    private Map<Integer, Writer> map;

    public ChatServer() {
        map = new ConcurrentHashMap();
    }

    public void addClient(Socket socket) throws IOException {
        if (socket == null) {
            throw new RuntimeException("客户端出现异常");
        }

        int port = socket.getPort();
        BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream())
        );
        map.put(port, writer);
        log.info("客户端[" + port + "] 成功连接");
    }

    public void removeClient(Socket socket) throws IOException {
        if (socket == null) {
            throw new RuntimeException("客户端出现异常");
        }

        int port = socket.getPort();
        if (!map.containsKey(port)) {
            return;
        }
        //关闭writer流, 即关闭socket流
        map.get(port).close();
        //移除对应key-value
        map.remove(port);
    }

    public void forwardMessage(Socket socket, String msg) throws IOException {
        if (socket == null) {
            throw new RuntimeException("客户端出现异常");
        }

        for (Integer i : map.keySet()) {
            if (i != socket.getPort()) {
                Writer writer = map.get(i);
                writer.write(msg);
                writer.flush();
            }
        }
    }

    public boolean quit(String msg) {
        return QUIT.equals(msg);
    }

    public void start() {
        try {
            //绑定socket, 监听端口
            serverSocket = new ServerSocket(PORT);
            log.info("服务器启动成功! 监听端口: " + PORT);

            while (true) {
                //获取客户端socket
                Socket socket = serverSocket.accept();
                //创建Handler线程去处理
                new Thread(new ChatHandler(this, socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(serverSocket);
        }
    }

    /**
     * 关闭serverSocket
     * 这个方法, 其实只有main线程会调用, 无需假synchronized,
     * 但是为了代码复用, 还是添加了双重检查机制
     * @param serverSocket
     */
    private void close(ServerSocket serverSocket) {
        if (!serverSocket.isClosed()) {
            synchronized (serverSocket) {
                if (!serverSocket.isClosed()) {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        ChatServer server = new ChatServer();
        server.start();
    }
}
